﻿using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Json;
using Xmart.Infrastructures.Office.Helpers;
using Xms.Abp.Application.Dtos;
using Xms.Abp.Authorization;
using Xms.Abp.Data;
using Xms.Abp.Domain;
using Xms.Abp.Domain.Repositories;
using Xms.Abp.Features;
using Xms.Abp.Identity;
using Xms.Abp.Linq;
using Xms.Abp.ObjectMapping;
using Xms.Abp.OperationLogging;

namespace Xms.Abp.Application.Services;

public abstract class XmsReadOnlyAppService<TEntity, TEntityDto, TKey>
    : XmsReadOnlyAppService<TEntity, TEntityDto, TKey, PagedSortedAndFilterParameter>,
    IXmsReadOnlyAppService<TEntityDto, TKey>
    where TEntity : class, IEntity<TKey>
    where TEntityDto : IEntityDto<TKey>
{
    protected XmsReadOnlyAppService(IXmsReadOnlyRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

public abstract class XmsReadOnlyAppService<TEntity, TEntityDto, TKey, TPagedParameter>
    : XmsReadOnlyAppService<TEntity, TEntityDto, TEntityDto, TKey, TPagedParameter>,
    IXmsReadOnlyAppService<TEntityDto, TKey, TPagedParameter>
    where TEntity : class, IEntity<TKey>
    where TEntityDto : IEntityDto<TKey>
    where TPagedParameter : class, IPagedSortedAndFilterParameter
{
    protected XmsReadOnlyAppService(IXmsReadOnlyRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

public abstract class XmsReadOnlyAppService<TEntity, TDetailsResult, TPagingListResult, TKey, TPagedParameter>
    : XmsReadOnlyAppService<TEntity, TDetailsResult, TPagingListResult, TDetailsResult, TKey, TPagedParameter>,
    IXmsReadOnlyAppService<TDetailsResult, TPagingListResult, TKey, TPagedParameter>
    where TEntity : class, IEntity<TKey>
    where TDetailsResult : IEntityDto<TKey>
    where TPagingListResult : IEntityDto<TKey>
    where TPagedParameter : class, IPagedSortedAndFilterParameter
{
    protected XmsReadOnlyAppService(IXmsReadOnlyRepository<TEntity, TKey> repository)
        : base(repository)
    {

    }
}

public abstract class XmsReadOnlyAppService<TEntity, TDetailsResult, TPagingListResult, TExportDto, TKey, TPagedParameter>
    : ReadOnlyAppService<TEntity, TDetailsResult, TPagingListResult, TKey, TPagedParameter>,
    IXmsReadOnlyAppService<TDetailsResult, TPagingListResult, TKey, TPagedParameter>
    where TEntity : class, IEntity<TKey>
    where TDetailsResult : IEntityDto<TKey>
    where TPagingListResult : IEntityDto<TKey>
    where TPagedParameter : class, IPagedSortedAndFilterParameter
{
    protected virtual int ExportMaxResultCount => 10000;
    protected virtual bool ExportDesensitization => false;
    protected virtual bool PageListDesensitization => true;
    protected IOperationLoggingManager OperationLoggingManage => LazyInject<IOperationLoggingManager>();
    protected IJsonSerializer JsonSerializer => LazyInject<IJsonSerializer>();
    protected new IFeatureChecker FeatureChecker => LazyInject<IFeatureChecker>();
    private IQueryableExecuter _QueryableExecuter;
    protected IQueryableExecuter QueryableExecuter
    {
        get
        {
            if (_QueryableExecuter == null)
            {
                _QueryableExecuter = AsyncExecuter as IQueryableExecuter;
                if (_QueryableExecuter == null)
                {
                    _QueryableExecuter = LazyInject<IQueryableExecuter>();
                }
            }
            return _QueryableExecuter;
        }
    }

    protected new IXmsObjectMapper ObjectMapper => base.ObjectMapper as IXmsObjectMapper;

    protected new IXmsReadOnlyRepository<TEntity, TKey> Repository { get; }
#pragma warning disable CS0809
    [Obsolete($"Use {nameof(DetailsPolicyName)}.")]
    protected sealed override string GetPolicyName => DetailsPolicyName;
    [Obsolete($"Use {nameof(PageListPolicyName)}.")]
    protected sealed override string GetListPolicyName => PageListPolicyName;
#pragma warning restore CS0809
    protected virtual string PageListPolicyName => GetPolicy();
    protected virtual string DetailsPolicyName => GetPolicy();
    protected virtual string ExportPolicyName => GetPolicy();
    protected virtual Dictionary<string, string> Policies { get; }
    protected virtual string GetPolicy([CallerMemberName] string memberName = null)
    {
        memberName = memberName.RemovePostFix("PolicyName").RemovePostFix("Async");
        return Policies.GetOrDefault(memberName);
    }

    protected XmsReadOnlyAppService(IXmsReadOnlyRepository<TEntity, TKey> repository) : base(repository)
    {
        Repository = repository;
        var type = this.GetType();
        var attr = type.GetCustomAttribute<PermissionsUseAttribute>();
        if (attr != null)
        {
            Policies = PermissionsConst.ToDictionary(attr.PermissionsConstType);
        }
        else
        {
            Policies = new();
        }
    }

    public TService LazyInject<TService>()
    {
        return LazyServiceProvider.LazyGetRequiredService<TService>();
    }

    [Obsolete($"Use {nameof(LazyInject)} method.")]
    public TService Inject<TService>()
    {
#pragma warning disable CS0618
        return ServiceProvider.GetRequiredService<TService>();
#pragma warning restore CS0618
    }

#pragma warning disable CS0809
    [Obsolete($"Use {nameof(CheckPageListPolicyAsync)} method.")]
    protected override async Task CheckGetListPolicyAsync()
    {
        await CheckPolicyAsync(PageListPolicyName);
    }

    [Obsolete($"Use {nameof(CheckDetailsPolicyAsync)} method.")]
    protected override async Task CheckGetPolicyAsync()
    {
        await CheckPolicyAsync(DetailsPolicyName);
    }
#pragma warning restore CS0809

    protected virtual async Task CheckPageListPolicyAsync()
    {
        await CheckPolicyAsync(PageListPolicyName);
    }

    protected virtual async Task CheckDetailsPolicyAsync()
    {
        await CheckPolicyAsync(DetailsPolicyName);
    }

    protected virtual async Task CheckExportPolicyAsync()
    {
        await CheckPolicyAsync(ExportPolicyName);
    }

    public override async Task<TDetailsResult> GetAsync(TKey id)
    {
        try
        {
            var result = await base.GetAsync(id);
            await FullingResultAuditingAsync(result);
            return result;
        }
        catch
        {
            return default;
        }
    }

    public virtual async Task<TDetailsResult> GetAsync(string no)
    {
        await CheckGetPolicyAsync();
        if (!typeof(IHasNo).IsAssignableFrom(typeof(TEntity)) || no.IsNullOrWhiteSpace())
        {
            return default;
        }
        var entity = await (await Repository.GetQueryableAsync()).Where(r => ((IHasNo)r).No == no).FirstOrDefaultAsync();
        var result = await MapToGetOutputDtoAsync(entity);
        try
        {
            await FullingResultDetailsAsync(result);
            return result;
        }
        catch
        {
            return default;
        }
    }

    protected override async Task<IQueryable<TEntity>> CreateFilteredQueryAsync(TPagedParameter input)
    {
        return await Repository.AsNoTrackingAsync();
    }

    public virtual new async Task<PagedResult<TPagingListResult>> GetListAsync(TPagedParameter input)
    {
        var result = await base.GetListAsync(input);
        await FullingResultAuditingAsync(result.Items);
        if (PageListDesensitization)
        {
            await DesensitizationResultDetailsAsync(result.Items);
        }
        var page = new PagedResult<TPagingListResult>(result.TotalCount, input.MaxResultCount, input.SkipCount, result.Items);
        return page;
    }

    protected virtual async Task<List<TPagingListResult>> GetListByRelationAsync<TRelationEntity, TRelationKey>(
        Expression<Func<TRelationEntity, bool>> dataSelector,
        Expression<Func<TRelationEntity, TRelationKey>> outerKeySelector,
        Expression<Func<TEntity, TRelationKey>> innerKeySelector,
        Expression<Func<TRelationEntity, TEntity, TEntity>> resultSelector,
        bool desensitization = false)
        where TRelationEntity : class, IEntity
        where TRelationKey : IEquatable<TRelationKey>
    {
        var repository = LazyInject<IXmsRepository<TRelationEntity>>();
        var query = (await repository.GetQueryableAsync()).Where(dataSelector).Join((await ReadOnlyRepository.GetQueryableAsync()), outerKeySelector, innerKeySelector, resultSelector);
        var list = await AsyncExecuter.ToListAsync(query);
        var result = ObjectMapper.Map<List<TEntity>, List<TPagingListResult>>(list);
        await FullingResultDetailsAsync(result);
        if (desensitization)
        {
            await DesensitizationResultDetailsAsync(result);
        }
        return result;
    }

    protected virtual async Task AddOperationLogAsync(string operation, string message, string source, string target, IUser user = null)
    {
        await OperationLoggingManage.AddAsync(operation, message, source, target, user);
    }

    protected virtual async Task AddOperationLogAsync<TData>(string operation, string message, TData source, TData target, IUser user = null)
    {
        await AddOperationLogAsync(operation, message, JsonSerializer.Serialize(source), JsonSerializer.Serialize(target), user);
    }

    protected virtual Task FullingResultDetailsAsync(TDetailsResult result)
    {
        return FullingResultDetailsAsync(new[] { result });
    }

    protected virtual async Task FullingResultDetailsAsync(IEnumerable<TPagingListResult> results)
    {
        await FullingResultDetailsAsync<TPagingListResult>(results);
    }

    protected virtual Task FullingResultDetailsAsync<TResult>(IEnumerable<TResult> results)
    {
        return Task.CompletedTask;
    }

    protected virtual Task DesensitizationResultDetailsAsync<TResult>(IEnumerable<TResult> results)
    {
        return Task.CompletedTask;
    }

    protected virtual async Task FullNameByRelationAsync<TRelationEntity, TResult, TRelationKey>(IEnumerable<TResult> results, Func<TResult, TRelationKey> keySelector, Action<TResult, string> nameSetter)
        where TRelationEntity : class, IEntity<TRelationKey>
    {
        var ids = results.Select(keySelector).Distinct();
        var repository = LazyInject<IXmsRepository<TRelationEntity, TRelationKey>>();
        var dict = (await (await repository.AsTrackingAsync()).Where(r => ids.Contains(r.Id)).ProjectTo<TRelationEntity, IdNameResult<TRelationKey>>().ToListAsync()).ToDictionary(r => r.Id, r => r.Name);
        foreach (var result in results)
        {
            var id = keySelector(result);
            if (dict.ContainsKey(id))
            {
                nameSetter(result, dict[id]);
            }
        }
    }

    protected virtual async Task FullNameByRelationAsync<TRelationEntity, TResult, TRelationKey>(TResult result, Func<TResult, TRelationKey> keySelector, Action<TResult, string> nameSetter)
        where TRelationEntity : class, IEntity<TRelationKey>
    {
        await FullNameByRelationAsync<TRelationEntity, TResult, TRelationKey>(new[] { result }, keySelector, nameSetter);
    }

    protected virtual Task FullingResultAuditingAsync(TDetailsResult result, bool isCreatorShowName = true, bool isLastModifierShowName = true, bool isDeleterShowName = true, bool isMaskName = false)
    {
        return ReadOnlyAppServiceExtensions.FullingResultAuditingAsync(this, result, isCreatorShowName, isLastModifierShowName, isDeleterShowName, isMaskName);
    }

    protected virtual Task FullingResultAuditingAsync(IEnumerable<TPagingListResult> results, bool isCreatorShowName = true, bool isLastModifierShowName = true, bool isDeleterShowName = true, bool isMaskName = false)
    {
        return ReadOnlyAppServiceExtensions.FullingResultAuditingAsync(this, results, isCreatorShowName, isLastModifierShowName, isDeleterShowName, isMaskName);
    }

    protected virtual Task<TExportDto> MapToExportDtoAsync(TEntity entity)
    {
        return Task.FromResult(MapToExportDto(entity));
    }

    protected virtual TExportDto MapToExportDto(TEntity entity)
    {
        return ObjectMapper.Map<TEntity, TExportDto>(entity);
    }

    public virtual async Task<(byte[], string)> ExportAsync(TPagedParameter input)
    {
        await CheckExportPolicyAsync();
        input.SkipCount = 0;
        input.MaxResultCount = ExportMaxResultCount;
        var query = await CreateFilteredQueryAsync(input);
        query = ApplySorting(query, input);
        query = ApplyPaging(query, input);
        var entities = await AsyncExecuter.ToListAsync(query);
        var entityDtos = new List<TExportDto>();
        foreach (var entity in entities)
        {
            entityDtos.Add(await MapToExportDtoAsync(entity));
        }
        await FullingResultDetailsAsync(entityDtos);
        if (ExportDesensitization)
        {
            await DesensitizationResultDetailsAsync(entityDtos);
        }
        var fileName = typeof(TExportDto).GetCustomAttribute<SheetAttribute>()?.SheetName ?? "";
        fileName = fileName.IsNullOrWhiteSpace() ? "" : (fileName + "_");
        fileName += DateTime.Now.ToString("yyyyMMdd") + "_" + GuidGenerator.Create().ToString("n");
        var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "exports", DateTime.Now.ToString("yyyyMMdd"));
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        path = Path.Combine(path, fileName);
        await ExcelHelper.WriteAsync(path, entityDtos);
        var result = await File.ReadAllBytesAsync(path);
        return (result, fileName);
    }

    protected virtual async Task<bool> IsGrantPolicyAsync(string policyName)
    {
        return await AuthorizationService.IsGrantedAsync(policyName);
    }

    public virtual async Task<List<NameValue<TKey>>> GetNameValuesAsync(string filter = null)
    {
        if (typeof(IHasDisplayName<TKey>).IsAssignableTo(typeof(TEntity)))
        {
            return await ((IQueryable<IHasDisplayName<TKey>>)(await Repository.AsNoTrackingAsync()))
                .Select(r => new NameValue<TKey> { Name = r.DisplayName, Value = r.Id }).ToListAsync();
        }
        if (typeof(IHasName<TKey>).IsAssignableTo(typeof(TEntity)))
        {
            return await ((IQueryable<IHasName<TKey>>)(await Repository.AsNoTrackingAsync()))
                .Select(r => new NameValue<TKey> { Name = r.Name, Value = r.Id }).ToListAsync();
        }
        return null;
    }

    public virtual async Task<List<NameValue>> GetNameNosAsync(string filter = null)
    {
        if (typeof(TEntity).IsAssignableTo(typeof(IHasNameNo)))
        {
            return await ((IQueryable<IHasNameNo>)(await Repository.AsNoTrackingAsync()))
                .WhereIf(!filter.IsNullOrWhiteSpace(), r => r.No == filter || r.Name.Contains(filter))
                .Select(r => new NameValue { Name = r.Name, Value = r.No }).ToListAsync();
        }
        return null;
    }
}

